﻿//===============================================================================
// Microsoft Innovation Centre - Western Europe
// Copyright (c) 2008 Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.ComponentModel;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.WVB.Framework.Events;
using Microsoft.WVB.Silverlight.Infrastructure;

namespace Microsoft.WVB.Framework
{
    public class UserManager<M,P> : IUserManager, INotifyPropertyChanged, IDisposable 
        where M: class, IMembershipUser, new()
        where P: class, IProfileInfo, new()
    {
        private IMembershipProvider<M> mP = null;
        private IProfileProvider<P> pP = null;
        private EventHandler appExitHandler = null;

        private CreateUserEventHandler<M> createUserHandler = null;
        private CreateProfileEventHandler<P> createProfileHandler = null;

        private EventHandler<NotifyActionCompletedEventArgs> validateUserHandler = null;
        private GetUserEventHandler<M> getUserHandler = null;
        private GetProfileEventHandler<P> getProfileHandler = null;

        private EventHandler<NotifyActionCompletedEventArgs> updateUserHandler = null;
        private EventHandler<NotifyActionCompletedEventArgs> updateProfileHandler = null;

        private Boolean IsPersisted = false;

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        public Boolean AllowCredentialCache
        {
            get { return _AllowCredentialCache; }
            set { _AllowCredentialCache = value; OnPropertyChanged(this.GetPropertySymbol(o=>o.AllowCredentialCache)); }// "AllowCredentialCache"); }
        }
        private Boolean _AllowCredentialCache;

        public IUser User
        {
            get { return _User; }
            set { _User = value; OnPropertyChanged(this.GetPropertySymbol( o => o.User)); }
        }
        private IUser _User;

        public String Message
        {
            get { return _Message; }
            //set { _Message = value; OnPropertyChanged(this.GetPropertySymbol(o => o.Message)); }
        }
        private String _Message;

        public UserManagerState UserState
        {
            get { return _UserState; }
            //set { _UserState = value; OnPropertyChanged(this.GetPropertySymbol(o => o.UserState)); }
        }
        private UserManagerState _UserState = UserManagerState.UnSigned;

        public Boolean IsAuthenticated
        {
            get { return _IsAuthenticated; }
            set { _IsAuthenticated = value; OnPropertyChanged(this.GetPropertySymbol(o => o.IsAuthenticated)); }
        }
        private Boolean _IsAuthenticated = false;

        public event RoutedEventHandler UserStateChanged;

        private const string ProfileStorageFilename = "profile.xml";
        private const string MembershipStorageFilename = "membership.xml";

        public UserManager()
        {
            _User = IoC.GetCreate<IUser>();
            mP = IoC.CurrentApplicationDIContainer.GetCreate<IMembershipProvider<M>>();
            pP = IoC.CurrentApplicationDIContainer.GetCreate<IProfileProvider<P>>();

            if (appExitHandler == null)
            {
                appExitHandler = delegate(object sender, EventArgs e)
                {
                    if ((!this.IsPersisted) && (this.AllowCredentialCache))
                    {
                        this.PersistInIsolatedStorage();
                        this.IsPersisted = true;
                    }
                };
            }
            Application.Current.Exit += appExitHandler;

            Initialize();
        }

        protected virtual void onUserStateChanged(UserManagerState s)
        {
            this._UserState = s;

            string message = default(string);
            switch (s)
            {
                case UserManagerState.Signed:
                    {
                        message = MessageTopics.Signed;
                        this.IsAuthenticated = true;
                    }
                    break;
                case UserManagerState.Failed:
                    {
                        message = MessageTopics.Failed;
                        this.IsAuthenticated = false;
                    }
                    break;
                case UserManagerState.SigningIn:
                    {
                        message = MessageTopics.SigningIn;
                    }
                    break;
                case UserManagerState.SigningUp:
                    {
                        message = MessageTopics.SigningUp;
                    }
                    break;
                case UserManagerState.UnSigned:
                    {
                        message = MessageTopics.UnSigned;
                        this.IsAuthenticated = false;
                    }
                    break;
                case UserManagerState.SigninOff:
                    {
                        message = MessageTopics.UserReady;
                    }
                    break;

                default:
                    message = string.Empty;
                    break;
            }

            if (this.UserStateChanged != null)
            {
                this.UserStateChanged(this, new RoutedEventArgs());
            }

            if (this.PropertyChanged != null)
            {
                this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(this.GetPropertySymbol(o => o.UserState)));
                this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(this.GetPropertySymbol(o => o.User)));
                this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(this.GetPropertySymbol(o => o.Message)));
            }
            
            IoC.SendNotification(Events.MessageTopics.UserStateChanged, message);
            //IoC.CurrentApplicationMediator.NotifyColleagues(Events.MessageTopics.UserStateChanged, message);
        }

        protected virtual void Initialize()
        {
            IsolatedStorageFile isofile = IsolatedStorageFile.GetUserStoreForApplication();

            // user already exist - create the User object based on the file content.
            if (isofile.FileExists(ProfileStorageFilename) && isofile.FileExists(MembershipStorageFilename))
            {
                _User = LoadFromIsolatedStorage();
                _Message = Strings.UserManager_Message_SuccessfullSignFromIsolatedStorage;
                onUserStateChanged(UserManagerState.Signed);
            }
            // User does not exist - first time loading the application, create an empty user.
            else
            {
                _User = new User<M,P>();
                onUserStateChanged(UserManagerState.UnSigned);
            }
        }

        private void Serialise<T>(T @object, XmlWriter writer) where T : class
        {
            XmlSerializer xs = new XmlSerializer(typeof(T), "http://dpewehq.microsoft.com/2008/06/WVB");
            xs.Serialize(writer, @object);
        }

        private T DeSerialize<T>(Stream s) where T : class, new()
        {
            XmlSerializer xs = new XmlSerializer(typeof(T), "http://dpewehq.microsoft.com/2008/06/WVB");
            if (xs.CanDeserialize(XmlReader.Create(s)))
            {
                s.Position = 0;
                return xs.Deserialize(s) as T;
            }
            else
                return new T();
        }

        protected virtual void PersistInIsolatedStorage()
        {
            IsolatedStorageFile isofile = IsolatedStorageFile.GetUserStoreForApplication();
            //User u = IoC.GetCreate<User>();

            if ((_User.ProfileInfo != null) && (_User.UserCredentials != null) && this.IsAuthenticated)
            {
                using (IsolatedStorageFileStream ifs = isofile.OpenFile(ProfileStorageFilename, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
                {
                    ifs.Position = 0;
                    XmlWriter writer = XmlWriter.Create(ifs);
                    Serialise<P>((P)_User.ProfileInfo, writer);
                    writer.Flush();
                    writer.Close();
                }

                using (IsolatedStorageFileStream ifs = isofile.OpenFile(MembershipStorageFilename, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
                {
                    ifs.Position = 0;
                    XmlWriter writer = XmlWriter.Create(ifs);
                    Serialise<M>((M)_User.UserCredentials, writer);
                    writer.Flush();
                    writer.Close();
                }
            }
        }

        protected virtual IUser LoadFromIsolatedStorage()
        {
            IsolatedStorageFile isofile = IsolatedStorageFile.GetUserStoreForApplication();
            M membership = null;
            P profile = null;

            // user already exist - create the User object based on the file content.
            if (isofile.FileExists(ProfileStorageFilename) && isofile.FileExists(MembershipStorageFilename))
            {
                using (IsolatedStorageFileStream ifs = isofile.OpenFile(ProfileStorageFilename, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    ifs.Position = 0;
                    profile = DeSerialize<P>(ifs);
                }

                using (IsolatedStorageFileStream ifs = isofile.OpenFile(MembershipStorageFilename, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    ifs.Position = 0;
                    membership = DeSerialize<M>(ifs);
                }
            }

            User<M,P> u = new User<M,P>();
            u.UserCredentials = membership;
            u.ProfileInfo = profile;

            return u;
        }

        public virtual void SignUp(string username, string password, string email, IProfileInfo profiledata)
        {
            if (createUserHandler == null)
            {
                createUserHandler = delegate(AcquireCompletedEventArgs<M> e)
                {
                    if ((!e.Cancelled) && (e.Error == null))
                    {
                        pP.CreateProfileAsync(e.UserState as P, e.Result);
                    }
                    else
                    {
                        string additionalInfo = "Not Specified";

                        if ((e.Error != null) && (!String.IsNullOrEmpty(e.Error.Message)))
                            additionalInfo = e.Error.Message;

                        _Message = String.Format(Strings.UserManager_Message_FailedSignUp, additionalInfo);
                        onUserStateChanged(UserManagerState.Failed);
                    }
                    mP.CreateUserCompleted -= createUserHandler;
                };
            }
            mP.CreateUserCompleted += createUserHandler;

            if (createProfileHandler == null)
            {
                createProfileHandler = delegate(AcquireCompletedEventArgs<P> e)
                {
                    if ((!e.Cancelled) && (e.Error == null))
                    {
                        _User.UserCredentials = e.UserState as M;
                        _User.ProfileInfo = e.Result;
                        _Message = String.Format(Strings.UserManager_Message_SuccessfulSignUp, _User.FirstName);
                        onUserStateChanged(UserManagerState.Signed);
                    }
                    else
                    {
                        string additionalInfo = "Not Specified";

                        if ((e.Error != null) && (!String.IsNullOrEmpty(e.Error.Message)))
                            additionalInfo = e.Error.Message;

                        _Message = String.Format(Strings.UserManager_Message_FailedSignUp, additionalInfo);
                        onUserStateChanged(UserManagerState.Failed);
                    }

                    pP.CreateProfileCompleted -= createProfileHandler;
                };
            }
            pP.CreateProfileCompleted += createProfileHandler;

            mP.CreateUserAsync(username, password, email, profiledata);
            onUserStateChanged(UserManagerState.SigningUp);
        }

        public virtual void SignIn(string username, string password)
        {
            if (validateUserHandler == null)
            {
                validateUserHandler = delegate(object sender, NotifyActionCompletedEventArgs e)
                {
                    if (e.Result)
                    {
                        mP.GetUserAsync(e.UserState as String, null);
                    }
                    else
                    {
                        string additionalInfo = Strings.UserManager_Message_NotSpecified;

                        if ((e.Error != null) && (!String.IsNullOrEmpty(e.Error.Message)))
                            additionalInfo = e.Error.Message;

                        _Message = String.Format(Strings.UserManager_Message_FailedSignIn, additionalInfo);
                        onUserStateChanged(UserManagerState.Failed);
                    }
                    mP.ValidateUserCompleted -= validateUserHandler;
                };
            }
            mP.ValidateUserCompleted += validateUserHandler;

            if (getUserHandler == null)
            {
                getUserHandler = delegate(AcquireCompletedEventArgs<M> e)
                {
                    if ((!e.Cancelled) && (e.Error == null))
                    {
                        pP.GetProfileAsync(e.Result.UserName, e.Result);
                    }
                    else
                    {
                        string additionalInfo = Strings.UserManager_Message_NotSpecified;

                        if ((e.Error != null) && (!String.IsNullOrEmpty(e.Error.Message)))
                            additionalInfo = e.Error.Message;

                        _Message = String.Format(Strings.UserManager_Message_FailedSignIn, additionalInfo);
                        onUserStateChanged(UserManagerState.Failed);
                    }
                    mP.GetUserCompleted -= getUserHandler;
                };
            }
            mP.GetUserCompleted += getUserHandler;

            if (getProfileHandler == null)
            {
                getProfileHandler = delegate(AcquireCompletedEventArgs<P> e)
                {
                    if ((!e.Cancelled) && (e.Error == null))
                    {
                        _User.UserCredentials = e.UserState as M;
                        _User.ProfileInfo = e.Result;
                        _Message = String.Format(Strings.UserManager_Message_SuccessfulSignin, _User.FirstName);
                        onUserStateChanged(UserManagerState.Signed);
                    }
                    else
                    {
                        string additionalInfo = Strings.UserManager_Message_NotSpecified;

                        if ((e.Error != null) && (!String.IsNullOrEmpty(e.Error.Message)))
                            additionalInfo = e.Error.Message;

                        _Message = String.Format(Strings.UserManager_Message_FailedSignIn, additionalInfo);
                        onUserStateChanged(UserManagerState.Failed);
                    }
                    pP.GetProfileCompleted -= getProfileHandler;
                };
            }
            pP.GetProfileCompleted += getProfileHandler;

            mP.ValidateUserAsync(username, password, username);
            onUserStateChanged(UserManagerState.SigningIn);
        }

        public virtual void SignOff()
        {
            onUserStateChanged(UserManagerState.SigninOff);
            _User.ProfileInfo = new P();
            _User.UserCredentials = new M();
            //TODO : WorkAround - password is part of the user object, So explicit cleaning necessary
            _User.Password = String.Empty;
            _User.RetypedPassword = String.Empty;
            // End Of Workaround
            _Message = Strings.UserManager_Message_SuccessfullSignOff;
            onUserStateChanged(UserManagerState.UnSigned);
        }

        public virtual void Update()
        {
            if (updateProfileHandler == null)
            {
                updateProfileHandler = delegate(object sender, NotifyActionCompletedEventArgs e)
                {
                    if ((e.Action == Action.Update) && (!e.Cancelled) && (e.Error == null) && (e.Result))
                    {
                        _Message = Strings.UserManager_Message_SuccessfulUpdate;
                        onUserStateChanged(UserManagerState.Updated);
                    }
                    else
                    {
                        string additionalInfo = Strings.UserManager_Message_NotSpecified;

                        if ((e.Error != null) && (!String.IsNullOrEmpty(e.Error.Message)))
                            additionalInfo = e.Error.Message;

                        _Message = String.Format(Strings.UserManager_Message_FailedUpdate, additionalInfo);
                        onUserStateChanged(UserManagerState.Failed);
                    }
                    pP.UpdateProfileCompleted -= updateProfileHandler;
                };
            }
            pP.UpdateProfileCompleted += updateProfileHandler;

            if (updateUserHandler == null)
            {
                updateUserHandler = delegate(object sender, NotifyActionCompletedEventArgs e)
                {
                    if ((e.Action == Action.Update) && (!e.Cancelled) && (e.Error == null) && (e.Result))
                    {
                        pP.UpdateProfileAsync((P)_User.ProfileInfo);
                    }
                    else
                    {
                        string additionalInfo = Strings.UserManager_Message_NotSpecified;

                        if ((e.Error != null) && (!String.IsNullOrEmpty(e.Error.Message)))
                            additionalInfo = e.Error.Message;

                        _Message = String.Format(Strings.UserManager_Message_FailedUpdate, additionalInfo);
                        onUserStateChanged(UserManagerState.Failed);
                    }
                    mP.UpdateUserCompleted -= updateUserHandler;
                };
            }
            mP.UpdateUserCompleted += updateUserHandler;

            mP.UpdateUserAsync((M)_User.UserCredentials);
            onUserStateChanged(UserManagerState.Updating);
        }

        public virtual void Reset()
        {
            _User = new User<M,P>();
            _Message = default(string);

            this.onUserStateChanged(UserManagerState.UnSigned);
        }

        public virtual void ClearCache()
        {
            IsolatedStorageFile.GetUserStoreForApplication().Remove();
        }

        public virtual void SetUserState(UserManagerState state)
        {
            onUserStateChanged(state);
        }

        public virtual void SetUserState(UserManagerState state, String message)
        {
            _Message = message;
            onUserStateChanged(state);
        }
        
        public void OnPropertyChanged(string property)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(property));
        }

        #region IDisposable Members

        public void Dispose()
        {
            if ((appExitHandler != null) && (!this.IsPersisted))
                appExitHandler.Invoke(this, new EventArgs());
        }

        #endregion
    }
}
